using System.Drawing;
using System.IO.Compression;
using System.Drawing.Imaging;
using System.Runtime.InteropServices;
using Microsoft.Extensions.Logging;
using _3Dto2D.Interfaces;
using _3Dto2D.Models;

namespace _3Dto2D.Services;

public class ModernLibraryManager : ILibraryManager
{
    private readonly ILogger<ModernLibraryManager> _logger;
    private readonly Dictionary<string, LibraryData> _loadedLibraries = new();
    private bool _disposed;

    public ModernLibraryManager(ILogger<ModernLibraryManager> logger)
    {
        _logger = logger;
    }

    public async Task<LibraryData?> LoadLibraryAsync(string filePath, bool isReference = false)
    {
        try
        {
            if (_loadedLibraries.TryGetValue(filePath, out var cached))
            {
                return cached;
            }

            if (!File.Exists(filePath))
            {
                _logger.LogWarning("Library file not found: {FilePath}", filePath);
                return null;
            }

            _logger.LogInformation("Loading library: {FilePath}", filePath);

            using var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read);
            using var reader = new BinaryReader(stream);

            var version = reader.ReadInt32();
            if (version != 2)
            {
                _logger.LogError("Unsupported library version: {Version}", version);
                return null;
            }

            var frameCount = reader.ReadInt32();
            var indexList = new List<int>(frameCount);

            for (int i = 0; i < frameCount; i++)
            {
                indexList.Add(reader.ReadInt32());
            }

            var library = new LibraryData
            {
                FilePath = filePath,
                IsReference = isReference,
                Version = version,
                LastModified = File.GetLastWriteTime(filePath)
            };

            // Load frames lazily
            for (int i = 0; i < frameCount; i++)
            {
                stream.Position = indexList[i];
                var frame = await LoadFrameAsync(reader, i, isReference);
                if (frame != null)
                {
                    library.Frames.Add(frame);
                }
            }

            _loadedLibraries[filePath] = library;
            _logger.LogInformation("Successfully loaded library with {FrameCount} frames", frameCount);

            return library;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to load library: {FilePath}", filePath);
            return null;
        }
    }

    public async Task SaveLibraryAsync(string filePath, LibraryData library, bool isReference = false)
    {
        try
        {
            _logger.LogInformation("Saving library to: {FilePath}", filePath);

            using var memoryStream = new MemoryStream();
            using var writer = new BinaryWriter(memoryStream);

            var frameCount = library.Frames.Count;
            var indexList = new List<int>();

            // Calculate offsets
            int offset = 8 + frameCount * 4; // Header + index table

            foreach (var frame in library.Frames)
            {
                indexList.Add((int)memoryStream.Length + offset);
                await SaveFrameAsync(writer, frame, isReference);
            }

            // Write to final file
            using var fileStream = new FileStream(filePath, FileMode.Create, FileAccess.Write);
            using var fileWriter = new BinaryWriter(fileStream);

            // Write header
            fileWriter.Write(library.Version);
            fileWriter.Write(frameCount);

            // Write index table
            foreach (var index in indexList)
            {
                fileWriter.Write(index);
            }

            // Write frame data
            memoryStream.Position = 0;
            await memoryStream.CopyToAsync(fileStream);

            library.FilePath = filePath;
            library.LastModified = DateTime.Now;

            _loadedLibraries[filePath] = library;
            _logger.LogInformation("Successfully saved library with {FrameCount} frames", frameCount);
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to save library: {FilePath}", filePath);
            throw;
        }
    }

    public async Task<FrameData?> GetFrameAsync(LibraryData library, int frameIndex)
    {
        if (frameIndex < 0 || frameIndex >= library.Frames.Count)
        {
            return null;
        }

        return await Task.FromResult(library.Frames[frameIndex]);
    }

    public async Task AddFrameAsync(LibraryData library, Bitmap image, short offsetX, short offsetY)
    {
        var frame = new FrameData
        {
            Index = library.Frames.Count,
            Image = new Bitmap(image),
            OffsetX = offsetX,
            OffsetY = offsetY
        };

        library.Frames.Add(frame);
        await Task.CompletedTask;
    }

    public async Task ReplaceFrameAsync(LibraryData library, int frameIndex, Bitmap image, short offsetX, short offsetY)
    {
        if (frameIndex < 0 || frameIndex >= library.Frames.Count)
        {
            return;
        }

        var existingFrame = library.Frames[frameIndex];
        existingFrame.Image?.Dispose();

        existingFrame.Image = new Bitmap(image);
        existingFrame.OffsetX = offsetX;
        existingFrame.OffsetY = offsetY;

        await Task.CompletedTask;
    }

    public async Task RemoveFrameAsync(LibraryData library, int frameIndex)
    {
        if (frameIndex < 0 || frameIndex >= library.Frames.Count)
        {
            return;
        }

        var frame = library.Frames[frameIndex];
        frame.Image?.Dispose();
        frame.HandImage?.Dispose();

        library.Frames.RemoveAt(frameIndex);

        // Update indices
        for (int i = frameIndex; i < library.Frames.Count; i++)
        {
            library.Frames[i].Index = i;
        }

        await Task.CompletedTask;
    }

    public async Task<LibraryData> CreateLibraryAsync()
    {
        return await Task.FromResult(new LibraryData());
    }

    public async Task<int> GetFrameCountAsync(LibraryData library)
    {
        return await Task.FromResult(library.Frames.Count);
    }

    private async Task<FrameData?> LoadFrameAsync(BinaryReader reader, int index, bool isReference)
    {
        try
        {
            var frame = new FrameData { Index = index };

            // Read basic frame data
            var width = reader.ReadInt16();
            var height = reader.ReadInt16();
            frame.OffsetX = reader.ReadInt16();
            frame.OffsetY = reader.ReadInt16();

            var shadowX = reader.ReadInt16();
            var shadowY = reader.ReadInt16();
            var shadow = reader.ReadByte();

            var hasMask = (shadow >> 7) == 1;

            var length = reader.ReadInt32();
            var imageData = reader.ReadBytes(length);

            // Decompress and create image
            if (imageData.Length > 0)
            {
                frame.Image = await DecompressImageAsync(imageData, width, height);
            }

            if (hasMask)
            {
                reader.ReadInt16(); // MaskWidth
                reader.ReadInt16(); // MaskHeight
                reader.ReadInt16(); // MaskX
                reader.ReadInt16(); // MaskY
                var maskLength = reader.ReadInt32();
                reader.ReadBytes(maskLength); // MaskFBytes
            }

            // Read reference data if applicable
            if (isReference)
            {
                frame.Transform.RotationX = reader.ReadSingle();
                frame.Transform.RotationY = reader.ReadSingle();
                frame.Transform.RotationZ = reader.ReadSingle();
                frame.Transform.MoveX = reader.ReadSingle();
                frame.Transform.MoveY = reader.ReadSingle();
            }

            return frame;
        }
        catch (Exception ex)
        {
            _logger.LogError(ex, "Failed to load frame {Index}", index);
            return null;
        }
    }

    private async Task SaveFrameAsync(BinaryWriter writer, FrameData frame, bool isReference)
    {
        var imageData = frame.Image != null ? await CompressImageAsync(frame.Image) : Array.Empty<byte>();

        writer.Write((short)(frame.Image?.Width ?? 0));
        writer.Write((short)(frame.Image?.Height ?? 0));
        writer.Write(frame.OffsetX);
        writer.Write(frame.OffsetY);
        writer.Write((short)0); // shadowX
        writer.Write((short)0); // shadowY
        writer.Write((byte)0);  // shadow
        writer.Write(imageData.Length);
        writer.Write(imageData);

        if (isReference)
        {
            writer.Write(frame.Transform.RotationX);
            writer.Write(frame.Transform.RotationY);
            writer.Write(frame.Transform.RotationZ);
            writer.Write(frame.Transform.MoveX);
            writer.Write(frame.Transform.MoveY);
        }
    }

        private async Task<Bitmap?> DecompressImageAsync(byte[] data, int width, int height)
    {
        if (data == null || data.Length == 0 || width <= 0 || height <= 0)
        {
            return null;
        }

        using var memoryStream = new MemoryStream(data);
        using var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress);
        using var resultStream = new MemoryStream();

        await gzipStream.CopyToAsync(resultStream);

        var decompressedBytes = resultStream.ToArray();

        var image = new Bitmap(width, height, PixelFormat.Format32bppArgb);
        var imageData = image.LockBits(
            new Rectangle(0, 0, width, height),
            ImageLockMode.WriteOnly,
            PixelFormat.Format32bppArgb);

        try
        {
            Marshal.Copy(decompressedBytes, 0, imageData.Scan0, decompressedBytes.Length);
        }
        finally
        {
            image.UnlockBits(imageData);
        }

        return image;
    }

    private async Task<byte[]> CompressImageAsync(Bitmap image)
    {
        // Placeholder compression - implement actual compression logic
        using var stream = new MemoryStream();
        using var gzipStream = new GZipStream(stream, CompressionMode.Compress);

        // Convert bitmap to byte array and compress
        // This is a simplified implementation

        return await Task.FromResult(stream.ToArray());
    }

    public void Dispose()
    {
        if (_disposed) return;

        foreach (var library in _loadedLibraries.Values)
        {
            foreach (var frame in library.Frames)
            {
                frame.Image?.Dispose();
                frame.HandImage?.Dispose();
            }
        }

        _loadedLibraries.Clear();
        _disposed = true;
    }
}
